{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Movebank API\n",
    "\n",
    "<img align=\"right\" src=\"https://anitagraser.github.io/movingpandas/assets/img/movingpandas.png\">\n",
    "\n",
    "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/anitagraser/movingpandas-examples/main?filepath=3-tech-demos/movebank.ipynb)\n",
    "\n",
    "\n",
    "Source of Python wrapper with functions using Movebank's REST API: https://github.com/movebank/movebank-api-doc/blob/master/mb_Meschenmoser.py\n",
    "\n",
    "<img align=\"left\" src=\"https://www.movebank.org/cms/img/logo-movebank.png\">\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use this notebook, you need a **Movebank account. Register at https://www.movebank.org**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "movebank_username = getpass.getpass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "movebank_password = getpass.getpass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Author: Philipp Meschenmoser, DBVIS, Uni Konstanz\n",
    "# Python wrapper with functions using Movebank's REST API to view available studies, read data and accept license terms programmatically\n",
    "# Acknowledgements to Anne K. Scharf and her great moveACC package, see https://gitlab.com/anneks/moveACC\n",
    "\n",
    "import requests\n",
    "import os\n",
    "import hashlib\n",
    "import csv\n",
    "import json\n",
    "import io\n",
    "from datetime import datetime, timedelta\n",
    "\n",
    "def callMovebankAPI(params):\n",
    "    # Requests Movebank API with ((param1, value1), (param2, value2),).\n",
    "    # Assumes the environment variables 'mbus' (Movebank user name) and 'mbpw' (Movebank password).\n",
    "    # Returns the API response as plain text.\n",
    "\n",
    "    response = requests.get('https://www.movebank.org/movebank/service/direct-read', params=params, auth=(movebank_username, movebank_password))\n",
    "    print(\"Request \" + response.url)\n",
    "    if response.status_code == 200:  # successful request\n",
    "        if 'License Terms:' in str(response.content):\n",
    "            # only the license terms are returned, hash and append them in a subsequent request.\n",
    "            # See also\n",
    "            # https://github.com/movebank/movebank-api-doc/blob/master/movebank-api.md#read-and-accept-license-terms-using-curl\n",
    "            print(\"Has license terms\")\n",
    "            hash = hashlib.md5(response.content).hexdigest()\n",
    "            params = params + (('license-md5', hash),)\n",
    "            # also attach previous cookie:\n",
    "            response = requests.get('https://www.movebank.org/movebank/service/direct-read', params=params,\n",
    "                                    cookies=response.cookies, auth=(movebank_username, movebank_password))\n",
    "            if response.status_code == 403:  # incorrect hash\n",
    "                print(\"Incorrect hash\")\n",
    "                return ''\n",
    "        return response.content.decode('utf-8')\n",
    "    print(str(response.content))\n",
    "    return ''\n",
    "\n",
    "\n",
    "def getStudies():\n",
    "    studies = callMovebankAPI((('entity_type', 'study'), ('i_can_see_data', 'true'), ('there_are_data_which_i_cannot_see', 'false')))\n",
    "    if len(studies) > 0:\n",
    "        # parse raw text to dicts\n",
    "        studies = csv.DictReader(io.StringIO(studies), delimiter=',')\n",
    "        return [s for s in studies if s['i_can_see_data'] == 'true' and s['there_are_data_which_i_cannot_see'] == 'false']\n",
    "    return []\n",
    "\n",
    "\n",
    "def getStudiesBySensor(studies, sensorname='GPS'):\n",
    "    return [s for s in studies if sensorname in s['sensor_type_ids']]\n",
    "\n",
    "\n",
    "def getIndividualsByStudy(study_id):\n",
    "    individuals = callMovebankAPI((('entity_type', 'individual'), ('study_id', study_id)))\n",
    "    if len(individuals) > 0:\n",
    "        return list(csv.DictReader(io.StringIO(individuals), delimiter=','))\n",
    "    return []\n",
    "\n",
    "\n",
    "def prettyPrint(l):\n",
    "    print(json.dumps(l, indent=2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## Movebank sensor options\n",
    "\n",
    "```\n",
    "description,external_id,id,is_location_sensor,name\n",
    "\"\",\"bird-ring\",397,true,\"Bird Ring\"\n",
    "\"\",\"gps\",653,true,\"GPS\"\n",
    "\"\",\"radio-transmitter\",673,true,\"Radio Transmitter\"\n",
    "\"\",\"argos-doppler-shift\",82798,true,\"Argos Doppler Shift\"\n",
    "\"\",\"natural-mark\",2365682,true,\"Natural Mark\"\n",
    "\"\",\"acceleration\",2365683,false,\"Acceleration\"\n",
    "\"\",\"solar-geolocator\",3886361,true,\"Solar Geolocator\"\n",
    "\"\",\"accessory-measurements\",7842954,false,\"Accessory Measurements\"\n",
    "\"\",\"solar-geolocator-raw\",9301403,false,\"Solar Geolocator Raw\"\n",
    "\"\",\"barometer\",77740391,false,\"Barometer\"\n",
    "\"\",\"magnetometer\",77740402,false,\"Magnetometer\"\n",
    "\"\",\"orientation\",819073350,false,\"Orientation\"\n",
    "\"\",\"solar-geolocator-twilight\",914097241,false,\"Solar Geolocator Twilight\"\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "allstudies = getStudies()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gpsstudies = getStudiesBySensor(allstudies, 'GPS')\n",
    "prettyPrint(gpsstudies)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "individuals = getIndividualsByStudy(study_id=2911040)\n",
    "prettyPrint(individuals)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get Movebank events and create a MovingPandas trajectory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import geopandas as gpd\n",
    "import movingpandas as mpd\n",
    "from shapely.geometry import Point\n",
    "import hvplot.pandas  # noqa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "STUDY_ID = 2911040\n",
    "INDIVIDUAL_ID = '2911059'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = (('entity_type', 'event'), ('study_id', STUDY_ID), ('individual_id', INDIVIDUAL_ID), ('sensor_type_id', 653))\n",
    "events_csv = callMovebankAPI(params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv(io.StringIO(events_csv))\n",
    "df.dropna(subset=['location_long', 'location_lat'], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf = gpd.GeoDataFrame(\n",
    "    df.drop(['location_long', 'location_lat'], axis=1),\n",
    "    crs='epsg:4326',\n",
    "    geometry=[Point(xy) for xy in zip(df.location_long, df.location_lat)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf.hvplot(title=f'Movebank events of individual {INDIVIDUAL_ID}', geo=True, tiles='OSM', frame_width=700, frame_height=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf['timestamp'] = pd.to_datetime(gdf['timestamp'])\n",
    "gdf.set_index('timestamp', inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "traj = mpd.Trajectory(gdf, 'tag_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "traj.hvplot(title=f'Trajectory of individual {INDIVIDUAL_ID}', c='speed', frame_width=700, frame_height=500, line_width=7.0, tiles='OSM', cmap='Viridis', colorbar=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
